// Copyright(c) Sonosemi Medical Ltd. Embedded Software Team.
// Author: Zhang Yisong(zhangyisong@sonosemi.com)

#pragma once
#include "boost/sml.hpp"
#include "cib/cib_connect.hpp"
#include "internal/concepts/active_event.hpp"
#include <stdx/ct_conversions.hpp>
#include <stdx/ct_string.hpp>
#include <tuple>
#include <type_traits>
namespace active {
namespace sml = boost::sml;

auto ErrName(auto e) {
  auto name = stdx::type_as_string<decltype(e)>();
  auto pos_begin = name.find("{\"");
  auto pos_end = name.find("\"}");
  return std::string_view{name.begin() + pos_begin + 2, name.begin() + pos_end};
}

namespace null {
struct ErrorHandler {
  auto operator()() {
    using namespace sml;
    return make_transition_table(
        *"error_listening"_s + unexpected_event<_> / [](const auto &error) {});
  }
};
} // namespace null

template <typename...> inline auto error_config = sml::sm<null::ErrorHandler>{};

/// @brief A template struct representing an event type with a variadic list of
/// argument types.
/// @tparam TArgs Variadic template arguments representing the data types stored
/// in the event.
template <typename TDerived, typename... TArgs> struct EventType {
  /// @brief The data type used to store the event data, which is a tuple of
  /// constant arguments.
  using DataType = std::tuple<const TArgs...>;

  /// @brief A nested struct representing the service callback metadata for the
  /// event type.
  // struct Service : cib::callback_meta<DataType> {};
  CIB_SERVICE_DEFINE(Service, DataType);
  // struct ConnectService : cib::callback_meta<DataType> {};
  CIB_SERVICE_DEFINE(ConnectService, DataType);

  /// @brief Construct a new EventType object.
  /// @param data The data to be stored in the event, a tuple of constant
  /// arguments.
  EventType(DataType data) : m_data(data) {}
  EventType(TArgs... args) : DataType(args...) {}
  /// @brief Get the data stored in the event object.
  /// @return A constant copy of the data tuple stored in the event object.
  auto Data() -> DataType const { return m_data; }

  /// @brief Overload the function call operator to get the data stored in the
  /// event object.
  /// @return A constant copy of the data tuple stored in the event object.
  auto operator()() -> DataType const { return m_data; }
  auto operator()() const -> DataType const { return m_data; }

  /// @brief Publish an event if the event publishing functionality is
  /// activated.
  /// @param args The arguments to be packed into a data tuple and published.
  static auto Publish(TArgs... args) {
    cib::CallService<Service>(DataType{args...});
    cib::CallService<ConnectService>(DataType{args...});
  };
  /// Build a simplex connection with a client event that will be published
  /// successively as the EventType::Publish(args...) method is called
  template <concepts::EventTyable... TClient> static auto Connect() {
    static_assert(
        std::conjunction_v<
            std::is_same<DataType, typename TClient::DataType>...>,
        "The data type of client-events must be the same as the server-event");
    static_assert(not std::conjunction_v<std::is_same<EventType, TClient>...>,
                  "The same event type is not allowed to be connect");
    cib::Connect<ConnectService>([](DataType data) {
      (cib::CallService<typename TClient::Service>(data), ...);
    });
  }
  /// Build a bidirectional connection between these two events so that the both
  /// events can be used interchangeably
  template <typename TEvent> static auto BiConnenct() {
    static_assert(
        std::is_same_v<DataType, typename TEvent::DataType>,
        "The data type of client-events must be the same as the server-event");
    static_assert(not std::is_same_v<TEvent, EventType>,
                  "The same event type is not allowed to be connect");
    TEvent::template Connect<EventType>();
    Connect<TEvent>();
  }
  static constexpr auto config = cib::config(cib::exports<Service>);

private:
  DataType m_data;
};

template <typename... Ts> inline auto ProcessEvent(auto e) {
  auto &config = error_config<Ts...>;
  config.process_event(e);
}
template <template <typename> typename TList, typename TEvent>
inline auto Publish(TList<TEvent>, auto... args) {
  using Type = std::remove_cvref_t<TEvent>;
  if constexpr (concepts::EventTyable<Type>) {
    Type::Publish(args...);
  } else {
    using Unexpected =
        std::remove_cvref_t<decltype(std::declval<TEvent>().event_)>;
    Unexpected::Publish(args...);
    ProcessEvent(Unexpected{typename Unexpected::DataType{args...}});
  }
}

namespace internal {

template <stdx::ct_string name, typename... TArgs>
struct EventLiteral : public active::EventType<EventLiteral<name>, TArgs...> {
  static constexpr std::string_view Name() {
    return std::string_view{name.value.begin(), name.value.end()};
  }
};
} // namespace internal
template <stdx::ct_string name, typename... TArgs> constexpr auto Event() {
  return boost::sml::event<internal::EventLiteral<name, TArgs...>>;
}

template <typename T> constexpr auto EventName(T) -> std::string_view {
  return std::string_view{"\0"};
}
template <typename T> constexpr auto EventName() -> std::string_view {
  return std::string_view{"\0"};
}

template <template <typename> typename TList, typename TEvent>
  requires concepts::EventTyable<std::remove_cvref_t<TEvent>> or
           concepts::EventTyable<
               std::remove_cvref_t<decltype(std::declval<TEvent>().event_)>>
constexpr auto EventName(TList<TEvent>) {
  using Type = std::remove_cvref_t<TEvent>;
  if constexpr (concepts::EventTyable<Type>) {
    return Type::Name();
  } else {
    return std::remove_cvref_t<decltype(std::declval<TEvent>().event_)>::Name();
  }
}
template <template <typename> typename TList, typename TEvent>
  requires concepts::EventTyable<std::remove_cvref_t<TEvent>> or
           concepts::EventTyable<
               std::remove_cvref_t<decltype(std::declval<TEvent>().event_)>>
constexpr auto EventName() {
  using Type = std::remove_cvref_t<TEvent>;
  if constexpr (concepts::EventTyable<Type>) {
    return Type::Name();
  } else {
    return std::remove_cvref_t<decltype(std::declval<TEvent>().event_)>::Name();
  }
}

} // namespace active
#define ACTIVE_EVENT_TYPE_DEF(__name__, ...)                                   \
  struct __name__                                                              \
      : public active::EventType<__name__ __VA_OPT__(, ) __VA_ARGS__> {        \
    static constexpr std::string_view Name() { return #__name__; }             \
  };

#define ATV_EVENT_TYPE_DEF(name, ...) ACTIVE_EVENT_TYPE_DEF(name, __VA_ARGS__)

#define ATV_EVT(name, ...)                                                     \
  auto name = atv::Event<#name __VA_OPT__(, ) __VA_ARGS__>()
